﻿using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using UtilZ.Dotnet.DBIBase;

namespace UtilZ.Dotnet.DBSqlServer
{
    internal partial class SQLServerDBAccess : DBAccessAbs
    {
        /// <summary>
        /// sql语句最大长度
        /// 65,536 * 网络数据包大小,网络数据包大小指的是用于在应用程序和关系 数据库引擎 之间进行通信的表格格式数据流(TDS) 数据包的大小。 默认的数据包大小为 4 KB，由“网络数据包大小”配置选项控制。
        /// </summary>
        private const long SQL_MAX_LENGTH = 268435456;
        public SQLServerDBAccess(IDBInteraction dbInteraction, DatabaseConfig config, string databaseTypeName)
            : base(dbInteraction, config, databaseTypeName, SQL_MAX_LENGTH)
        {

        }

        /// <summary>
        /// 最优化表,回收不使用的空间,并整理数据文件的碎片
        /// </summary>
        /// <param name="tableNames">表名列表,如果为null或空,则全部执行</param>
        protected void PrimitiveOptimizeTable_bk(List<string> tableNames)
        {
            /********************************************************************************
             * 查了下原因，根本原因是删除数据并不释放表空间，日志文件太过巨大的原因。
             * 网上查了查，解决方案如下：
             * 
             * 第一步, 在收缩前先查看日志的大小:
             * SELECT *
             * FROM sysfiles
             * WHERE name LIKE '%LOG%'
             * GO
             *   
             * 第二步, 把数据库的恢复模式设成”简单”:
             * ALTER DATABASE [数据库名] SET RECOVERY SIMPLE WITH NO_WAIT
             * GO
             * ALTER DATABASE [数据库名] SET RECOVERY SIMPLE
             * GO
             *   
             * 第三步, 运行checkpoint指令, 把dirty page写进数据库:
             * CHECKPOINT
             * GO
             *    
             * 第四步, 截断日志:
             * BACKUP LOG 库名 WITH NO_LOG
             * GO
             *   
             * 第五步, 记录一下日志名为下一步做准备:
             * SELECT Name FROM sysfiles WHERE name LIKE '%LOG'
             * GO
             *    
             * 第六步, 收缩日志文件, 把不用的空间释放给操作系统:
             * DBCC SHRINKFILE (文件名, 所需大小)
             * GO
             *   
             * 第七步, 验证一下日志大小是否达到所需大小了:
             * SELECT *
             * FROM sysfiles
             * WHERE name LIKE '%LOG%'
             * GO
             *  
             * 第八步，将模式设置回去
             * ALTER DATABASE [数据库名] SET RECOVERY FULL WITH NO_WAIT
             * GO
             * ALTER DATABASE [数据库名] SET RECOVERY FULL
             * GO
             *  
             * --查询指定数据库的日志文件名称
             * USE [数据库名]
             * GO
             * SELECT name FROM SYS.database_files WHERE type_desc='LOG'
             *  
             *  
             *  后续步骤:
             *  1)查看你的主要数据库, 看看日志增长是否失控;
             *  2)查看上面的代码, 并进行必要修改和测试以适应你的数据库要求;
             *  3)定期收缩数据库 ----(注: 慎用!! 不建议用在production环境里.)
             *  4)继续监控数据库大小和服务器上的可用空间大小.
             * 
             * 释放日志空间：DBCC SHRINKFILE (文件名, 所需大小)
             * 释放表空间：DBCC SHRINKDATABASE (库名,所需大小);
             ********************************************************************************/

            IDatabase database = this.Database;
            string databaseName = database.GetDatabaseName();
            using (var con = base.CreateConnection(DBVisitType.W))
            {
                var transaction = con.DbConnection.BeginTransaction();

                //把数据库的恢复模式设成”简单”
                var cmd1 = con.DbConnection.CreateCommand();
                cmd1.Transaction = transaction;
                cmd1.CommandText = $"ALTER DATABASE {databaseName} SET RECOVERY SIMPLE WITH NO_WAIT";
                cmd1.ExecuteNonQuery();

                var cmd2 = con.DbConnection.CreateCommand();
                cmd2.Transaction = transaction;
                cmd2.CommandText = $"ALTER DATABASE {databaseName} SET RECOVERY SIMPLE";
                cmd2.ExecuteNonQuery();

                //运行checkpoint指令, 把dirty page写进数据库:
                var cmd3 = con.DbConnection.CreateCommand();
                cmd3.Transaction = transaction;
                cmd3.CommandText = "CHECKPOINT";
                cmd3.ExecuteNonQuery();

                //截断日志
                var cmd4 = con.DbConnection.CreateCommand();
                cmd4.Transaction = transaction;
                cmd4.CommandText = $"BACKUP LOG {databaseName} WITH NO_LOG";
                cmd4.ExecuteNonQuery();

                //得到日志名,为下一步做准备
                var cmd5 = con.DbConnection.CreateCommand();
                cmd5.Transaction = transaction;
                cmd5.CommandText = "SELECT Name FROM sysfiles WHERE name LIKE '%LOG'";
                string logFileName = (string)cmd5.ExecuteScalar();

                //收缩日志文件, 把不用的空间释放给操作系统:
                var cmd6 = con.DbConnection.CreateCommand();
                cmd6.Transaction = transaction;
                cmd6.CommandText = $"DBCC SHRINKFILE ({logFileName}, 所需大小)";
                cmd6.ExecuteNonQuery();

                //将模式设置回去
                var cmd7 = con.DbConnection.CreateCommand();
                cmd7.Transaction = transaction;
                cmd7.CommandText = $"ALTER DATABASE {databaseName} SET RECOVERY FULL WITH NO_WAIT";
                cmd7.ExecuteNonQuery();

                var cmd8 = con.DbConnection.CreateCommand();
                cmd8.Transaction = transaction;
                cmd8.CommandText = $"ALTER DATABASE {databaseName} SET RECOVERY FULL";
                cmd8.ExecuteNonQuery();

                try
                {
                    transaction.Commit();
                }
                catch
                {
                    transaction.Rollback();
                    throw;
                }
            }
        }


        /// <summary>
        /// 最优化表,回收不使用的空间,并整理数据文件的碎片
        /// </summary>
        /// <param name="tableNames">表名列表,如果为null或空,则全部执行</param>
        protected override void PrimitiveOptimizeTable(List<string> tableNames)
        {
            /********************************************************************************
             * 阿里云售后工程师很认真的给了我回收语句。dbcc shrinkfile(N'testdb',0,truncateonly)但是并没有效果。
             * 又一次询问，给了新的语句。
             * --1.查看数据库总空间，已用空间，未用空间，使用率
             * USE $DB
             * GO
             * SELECT a.name as 逻辑文件名, size / 128[totalspace文件大小(兆)], 
             *     FILEPROPERTY(a.name, 'SpaceUsed') / 128[usedspace已用空间(兆)],
             *     size / 128 - FILEPROPERTY(a.name, 'SpaceUsed') / 128[未用空间(兆)],
             *     FILEPROPERTY(a.name, 'SpaceUsed') * 100.0 / size[使用率(%)]
             * FROM sys.database_files a cross join(select recovery_model_desc, log_reuse_wait, log_reuse_wait_desc, is_auto_shrink_on from sys.databases where name= DB_NAME())b
             * WHERE type = 0
             * 
             *  --2.进行回收
             * declare @usedspace int , @totalspace int
             * select @usedspace = xxx,@totalspace = yyy
             * while @totalspace > @usedspace
             * begin
             *     set @totalspace = @totalspace - 5 * 1024
             *     DBCC SHRINKFILE(逻辑文件名, @totalspace )
             * end
             * --逻辑文件名，usedspace，totalspace从1中的结果集获取
             * 
             * 这样回收后，未用空间终于降下去了，使用率从1 % 回到了40 %。当然回收前进行一次全量备份还是必须的。
            ********************************************************************************/

            using (var con = base.CreateConnection(DBVisitType.W))
            {
                //把数据库的恢复模式设成”简单”
                var cmd1 = con.DbConnection.CreateCommand();
                cmd1.CommandText = @"SELECT a.name as 逻辑文件名, size/128 [totalspace文件大小(兆)], 
    FILEPROPERTY(a.name, 'SpaceUsed')/128 [usedspace已用空间(兆)],
    size/128 - FILEPROPERTY(a.name, 'SpaceUsed')/128 [未用空间(兆)],
    FILEPROPERTY(a.name, 'SpaceUsed')*100.0/size [使用率(%)]
FROM sys.database_files a cross join (select recovery_model_desc, log_reuse_wait,log_reuse_wait_desc,is_auto_shrink_on  from sys.databases where name=DB_NAME())b
WHERE type=0";
                string fileName;
                double usedspaceMB = 0d, totalspaceMB = 0d;
                using (var reader = cmd1.ExecuteReader())
                {
                    if (!reader.Read())
                    {
                        return;
                    }

                    fileName = reader.GetString(0);
                    usedspaceMB = Convert.ToInt32(reader.GetValue(1));
                    totalspaceMB = Convert.ToInt32(reader.GetValue(2));
                }

                var cmd2 = con.DbConnection.CreateCommand();
                while (totalspaceMB > usedspaceMB)
                {
                    totalspaceMB = totalspaceMB - 5 * 1024;
                    cmd2.CommandText = $"DBCC SHRINKFILE({fileName}, {totalspaceMB})";
                    cmd2.ExecuteNonQuery();
                }
            }
        }

        /// <summary>
        /// 创建数据库对象
        /// </summary>
        /// <returns></returns>
        protected override IDatabase CreateDatabase()
        {
            return new SqlServerDatabase(this);
        }
    }
}
